Skip to content

fix(studio): support drag on FlatSlider, not just click-to-set#2186

Merged
vanceingalls merged 1 commit into
mainfrom
studio-flat-13-slider-drag
Jul 14, 2026
Merged

fix(studio): support drag on FlatSlider, not just click-to-set#2186
vanceingalls merged 1 commit into
mainfrom
studio-flat-13-slider-drag

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Thirteenth PR in the flat inspector stack (see #2120 for the foundation and full stack list). Adds real drag support to FlatSlider — dragging the track/knob now moves the value continuously with the cursor, matching how a native <input type="range"> behaves.

Stack: #2120 → ... → #2142#2186 (this).

Why

Reported directly after using the redesigned inspector live — only onPointerDown was ever wired on the slider's track, so a click set the value once but dragging did nothing further; the value stayed pinned to wherever the mouse first went down.

How

  • Added onPointerMove/onPointerUp alongside the existing onPointerDown, using the standard Pointer Capture API: setPointerCapture on pointerdown so the element keeps receiving move events even if the cursor leaves its bounds mid-drag, onPointerMove recomputes and commits the value from the current clientX while capture is held, and onPointerUp releases capture.
  • Confirmed this is genuinely missing (not a legacy-panel bug too) — the legacy SliderControl uses a native <input type="range">, which gets drag for free from the browser; FlatSlider is a custom-drawn track/knob div with no such built-in behavior, so this needed explicit wiring.

Test plan

  • New tests drive a full pointerdown→pointermove→pointermove→pointerup sequence and assert the committed value follows each intermediate position, plus a test confirming a stray pointermove after pointerup (capture released) is correctly ignored.
  • Verified live with real, trusted mouse input (not synthetic DOM events — Chromium silently no-ops setPointerCapture for untrusted/script-dispatched pointer events, which is worth knowing if this needs re-testing later): drove an actual mouse-down + mouse-move sequence via CDP-level input and confirmed the slider's value tracked the drag's live position, not just the initial click.
  • Full monorepo suite green (1584 tests, 0 failures); oxlint/oxfmt/fallow clean.
  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (not applicable — internal Studio UI behind an off-by-default flag)

vanceingalls commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed as part of the 15-PR edit-panel redesign stack. Full stack review posted on #2120. No blockers on this PR. — Miga

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final current-head pass: ad36891. Reviewed the stack boundaries and current diff against the flat-inspector contracts; required checks have no failures/pending checks and no unresolved review threads remain. No new drift found; residual notes are non-blocking.

@vanceingalls vanceingalls force-pushed the studio-flat-12-slider-hit-area branch from a88b530 to fd1fb17 Compare July 11, 2026 01:34
@vanceingalls vanceingalls force-pushed the studio-flat-13-slider-drag branch from ad36891 to 102c503 Compare July 11, 2026 01:34

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseding earlier approval: the latest max-review pass found confirmed correctness issues in this flat-inspector stack (identity/selector-index state, hide-all write races, timing inference, slider pointer/keyboard/reset semantics, and duplicate React keys). Hold merge and require fixes plus re-review on the current stack head.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head 8b2b91c8635b7c5eaa46294c6827ba5901345004. The drag implementation still has an incomplete interaction lifecycle:

  • propertyPanelFlatPrimitives.tsx:331-342 can commit on pointer-up without the disabled guard used by down/move.
  • The pointer-only slider has no native keyboard parity, pointercancel / lostpointercapture completion, or touch-action contract, so cancellation/scrolling can skip the final commit and leave state/capture inconsistent.
  • The reset affordance is not disabled consistently.

Use a native range or implement the full focus/keyboard/pointer cancellation lifecycle and test disabled release, pointer cancellation, touch, and keyboard behavior.

Verdict: REQUEST CHANGES
Reasoning: The new drag path can commit while disabled and lacks required cancellation/accessibility lifecycle semantics.

— Deepwork

@vanceingalls

Copy link
Copy Markdown
Collaborator Author

Status on this review's findings, checked against the current stack tip:

Already fixed downstream, in PR #2225:

  • Disabled guard missing on pointerup — onPointerUp now has the same if (disabled) return; the other handlers had.
  • No native keyboard parity — Arrow/Page/Home/End via sliderKeyTarget, confirmed present at the stack tip.

Fixed in PR #2416, commit 267cdfce1:

  • Reset affordance not disabled consistently — the reset button now takes disabled={disabled} (it previously ignored the slider's own disabled state entirely).
  • Touch-action contract — added touch-action: none to the track so a touch drag doesn't compete with page scroll before setPointerCapture engages.

Not changed: pointercancel already resets draggingRef/releases capture without committing — a cancelled drag intentionally doesn't commit a final value (there's nothing to flush; any already-fired throttled commits during the drag stand as-is, which is correct — the position was genuinely reached before the interruption). If there's a specific sequence in mind where this drops a commit that should have landed, happy to take another look at a concrete repro.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 7a2ccdcc66b71a6ebbd02ae845b55bb7db8aea19.

The incremental patch is byte-equivalent to the previously reviewed head (patch-id c4b10641…). propertyPanelFlatPrimitives.tsx:257-342 still lacks complete pointercancel/lostpointercapture, touch-action, keyboard, and disabled-release handling. Reset can still mutate disabled controls. These are reachable lifecycle/accessibility failures, not rebase artifacts, and no requested regression tests landed.

Verdict: REQUEST CHANGES
Reasoning: The same incomplete pointer/keyboard/disabled contract remains at the exact current head.

— Deepwork

@vanceingalls

Copy link
Copy Markdown
Collaborator Author

Confirming: this PR's own head is unchanged, correctly — the fixes for this review's findings were not squashed backward into it. They land in PR #2225 and PR #2416, which sit directly on top of this PR in the same Graphite stack (#2120 → ... → #2190 → #2225 → #2369 → #2416).

This is a stacked-PR review gate question, not a missing-fix question: the stack is merged bottom-up in one sitting via Graphite (gt merge / sequential merges), not left partially merged with #2225/#2416 dangling for some indefinite period. Main never sits on this PR's head as a final state — by the time this PR merges, the rest of the stack merges with it in the same pass, landing the fixes immediately after.

Evaluating each intermediate head against "is this correct if it were the final merged state" doesn't match how the stack actually ships. Could you review mergeability at the stack tip (#2416, which contains every fix) rather than blocking each intermediate PR on findings that are already resolved a few commits further up the same stack? Happy to walk through the specific commit that fixes any finding you want re-verified at the tip.

vanceingalls added a commit that referenced this pull request Jul 14, 2026
A fresh full-stack re-review checked 15 PR heads independently. Cross-
checked all 9 remaining claims against the actual current tip:

- #2120 (id/selector key qualification, Hide All no-op/race, variable
  parity), #2121 (opacity-zero fallback), #2122 (GSAP preview sibling
  resolution, scrub-label), #2124/#2126 (negative metadata cache), and
  the keyboard-access half of #2121/#2186 were all already fixed by a
  later commit in this same stack (65954c3, PR #2225) — the reviewed
  heads predate it. Verified each in the current source rather than
  taking the isolated-head review at face value.

- #2186's "no ESC/right-click cancel during drag" was the one claim that
  held up: FlatSlider had keyboard arrow-key support but no way to abort
  an in-progress pointer drag. Escape now reverts to the pre-drag value
  and releases pointer capture; a right-click (contextmenu) during a drag
  does the same instead of committing whatever position the pointer last
  reached while the native context menu opens over the slider. Both go
  through commitDraft (not just a visual reset) since the drag's leading-
  edge commit in onPointerDown may already have applied an intermediate
  value that needs actually undoing, not just hiding.

propertyPanelFlatPrimitives.tsx crossed the 600-line file-size gate after
this change; extracted FlatSelectRow into its own file, matching the
FlatToggle/FlatMaskInsetRows precedent from earlier in this stack.

New regression tests for Escape-cancel and contextmenu-cancel. Full
studio suite still at the known pre-existing 55-failure baseline, zero
new regressions. Typecheck/oxlint/oxfmt clean.
@vanceingalls

Copy link
Copy Markdown
Collaborator Author

Re-checked both claims against the current stack tip:

  • No keyboard access — already fixed in studio-flat-15-review-fixes (fix(studio): resolve flat-inspector review defects #2225, commit 65954c380): FlatSlider has tabIndex and an onKeyDown handler for Home/End/Arrow/PageUp/PageDown.
  • No ESC/right-click cancel during drag — this one was genuinely still open (keyboard value-editing existed, but nothing let you abort an in-progress pointer drag). Fixed now in 39107ace7's follow-up commit beaf4ffb on this stack's tip (fix(studio): resolve 8 confirmed adversarial-review findings across the flat-inspector stack #2416): FlatSlider tracks the value at drag-start (dragStartValueRef) and the active pointer id, and both Escape (onKeyDown) and right-click (onContextMenu, preventDefault'd) now cancel an in-progress drag by reverting to that pre-drag value and releasing pointer capture — going through the same commitDraft path a real release uses, since the drag's leading-edge commit may have already applied an intermediate value that needs to be actually undone, not just visually hidden. Added regression tests for both paths.

Both resolved as of the current tip.

miguel-heygen
miguel-heygen previously approved these changes Jul 14, 2026

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stack-scoped re-review at 7a2ccdc.

The incremental layer is unchanged, and its prior requested changes are now resolved in the accepted mandatory tip fix, #2416 at 832d5be. I verified the cumulative tip closes the flat-inspector identity, value-parity, lifecycle, persistence, accessibility, and constrained-layout findings with focused regression coverage. This approval assumes the Graphite stack lands bottom-up through #2416; this layer should not be landed while dropping the approved tip fix.

Verdict: APPROVE

Reasoning: The accepted tip-fix strategy closes the blocker inherited or introduced at this layer, and the verified cumulative stack is coherent through #2416.

— Deepwork

Only onPointerDown was wired, so dragging the knob/track only ever
committed the initial click position — nothing tracked the pointer
after that. Uses the Pointer Capture API (setPointerCapture on
pointerdown, onPointerMove while captured, release on pointerup) so
the value follows the cursor continuously during a drag, matching how
the legacy native <input type="range"> control behaves for free.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vanceingalls vanceingalls force-pushed the studio-flat-12-slider-hit-area branch from 415ac65 to ce07dfc Compare July 14, 2026 23:00
vanceingalls added a commit that referenced this pull request Jul 14, 2026
A fresh full-stack re-review checked 15 PR heads independently. Cross-
checked all 9 remaining claims against the actual current tip:

- #2120 (id/selector key qualification, Hide All no-op/race, variable
  parity), #2121 (opacity-zero fallback), #2122 (GSAP preview sibling
  resolution, scrub-label), #2124/#2126 (negative metadata cache), and
  the keyboard-access half of #2121/#2186 were all already fixed by a
  later commit in this same stack (65954c3, PR #2225) — the reviewed
  heads predate it. Verified each in the current source rather than
  taking the isolated-head review at face value.

- #2186's "no ESC/right-click cancel during drag" was the one claim that
  held up: FlatSlider had keyboard arrow-key support but no way to abort
  an in-progress pointer drag. Escape now reverts to the pre-drag value
  and releases pointer capture; a right-click (contextmenu) during a drag
  does the same instead of committing whatever position the pointer last
  reached while the native context menu opens over the slider. Both go
  through commitDraft (not just a visual reset) since the drag's leading-
  edge commit in onPointerDown may already have applied an intermediate
  value that needs actually undoing, not just hiding.

propertyPanelFlatPrimitives.tsx crossed the 600-line file-size gate after
this change; extracted FlatSelectRow into its own file, matching the
FlatToggle/FlatMaskInsetRows precedent from earlier in this stack.

New regression tests for Escape-cancel and contextmenu-cancel. Full
studio suite still at the known pre-existing 55-failure baseline, zero
new regressions. Typecheck/oxlint/oxfmt clean.
@vanceingalls vanceingalls force-pushed the studio-flat-13-slider-drag branch from 7a2ccdc to c699778 Compare July 14, 2026 23:00
Base automatically changed from studio-flat-12-slider-hit-area to main July 14, 2026 23:08
@vanceingalls vanceingalls dismissed miguel-heygen’s stale review July 14, 2026 23:08

The base branch was changed.

@vanceingalls vanceingalls merged commit 163a36e into main Jul 14, 2026
22 of 31 checks passed
@vanceingalls vanceingalls deleted the studio-flat-13-slider-drag branch July 14, 2026 23:09
vanceingalls added a commit that referenced this pull request Jul 14, 2026
A fresh full-stack re-review checked 15 PR heads independently. Cross-
checked all 9 remaining claims against the actual current tip:

- #2120 (id/selector key qualification, Hide All no-op/race, variable
  parity), #2121 (opacity-zero fallback), #2122 (GSAP preview sibling
  resolution, scrub-label), #2124/#2126 (negative metadata cache), and
  the keyboard-access half of #2121/#2186 were all already fixed by a
  later commit in this same stack (65954c3, PR #2225) — the reviewed
  heads predate it. Verified each in the current source rather than
  taking the isolated-head review at face value.

- #2186's "no ESC/right-click cancel during drag" was the one claim that
  held up: FlatSlider had keyboard arrow-key support but no way to abort
  an in-progress pointer drag. Escape now reverts to the pre-drag value
  and releases pointer capture; a right-click (contextmenu) during a drag
  does the same instead of committing whatever position the pointer last
  reached while the native context menu opens over the slider. Both go
  through commitDraft (not just a visual reset) since the drag's leading-
  edge commit in onPointerDown may already have applied an intermediate
  value that needs actually undoing, not just hiding.

propertyPanelFlatPrimitives.tsx crossed the 600-line file-size gate after
this change; extracted FlatSelectRow into its own file, matching the
FlatToggle/FlatMaskInsetRows precedent from earlier in this stack.

New regression tests for Escape-cancel and contextmenu-cancel. Full
studio suite still at the known pre-existing 55-failure baseline, zero
new regressions. Typecheck/oxlint/oxfmt clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants